package com.common.util;

import com.common.consts.AuthHeaders;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author: zhaojd1604@gmail.com
 * @date: 2019-01-04 16:52
 * @description:
 * @since:
 */

public enum SignUtil {
    ;

    private static final Logger logger = LoggerFactory.getLogger(SignUtil.class);
    /**
     * 编码格式
     */
    private static final String ENCODING = "UTF-8";
    /**
     * 参数分隔符
     */
    private static final String SEPARATOR = "&";
    /**
     * 签名算法
     */
    private static final String ALGORITHM = "HmacSHA256";

    /**
     * 生成数据签名
     *
     * @param method
     * @param params
     * @param accessKeySecret
     * @return
     */
    public static String sign(String method, Map<String, String> params, String accessKeySecret) {
        //生成待签名串
        String signString = generateSignString(method, params);
        //签名
        return digitalSignature(signString, accessKeySecret);
    }

    /**
     * 生成待签名字符串，
     * 待签名字符串 = 请求方法 + 请求参数（Header参数+Querystring参数）按既定格式拼接而成。格式如下：
     * HttpMethod+UrlEncode("?")+UrlEncode(paramName1)+"="+UrlEncode(paramValue1)+"&"+UrlEncode(paramName2)+"="+UrlEncode(paramValue2)+...
     * <p>
     * 注意：请求参数需按字典顺序排序
     *
     * @param method
     * @param params
     * @return
     */
    public static String generateSignString(String method, Map<String, String> params) {
        // 参数排序
        List<String> sortedKeys = params.keySet().stream()
                .sorted()
                .collect(Collectors.toList());

        // 拼接请求方法
        StringBuilder signStringBuilder = new StringBuilder();
        try {
            signStringBuilder.append(method.toUpperCase()).append(encode("?"));
        } catch (UnsupportedEncodingException e) {
            logger.error("拼接签名串异常！", e);
        }

        // 拼接请求参数
        StringBuilder paramStringBuilder = new StringBuilder();
        sortedKeys.forEach(key -> {
            try {
                paramStringBuilder.append(SEPARATOR)
                        .append(encode(key))
                        .append("=")
                        .append(encode(params.get(key)));
            } catch (UnsupportedEncodingException e) {
                logger.error("拼接签名串异常，key为{}", key, e);
            }
        });
        signStringBuilder.append(paramStringBuilder.toString().substring(1));

        return signStringBuilder.toString();
    }

    /**
     * 对待签名字符串进行数据签名
     *
     * @param data
     * @param secret
     * @return
     */
    public static String digitalSignature(String data, String secret) {
        byte[] sign = null;
        try {
            Mac mac = Mac.getInstance(ALGORITHM);
            mac.init(new SecretKeySpec(secret.getBytes(ENCODING), ALGORITHM));
            sign = mac.doFinal(data.getBytes(ENCODING));
        } catch (NoSuchAlgorithmException e) {
            logger.error(e.getMessage(), e);
        } catch (InvalidKeyException e) {
            logger.error(e.getMessage(), e);
        } catch (UnsupportedEncodingException e) {
            logger.error(e.getMessage(), e);
        }
        if (null != sign) {
            return new String(Base64.getEncoder().encode(sign));
        } else {
            return null;
        }
    }

    /**
     * 根据RFC3986规范进行UrlEncode
     *
     * @param value
     * @return
     * @throws UnsupportedEncodingException
     */
    private static String encode(String value) throws UnsupportedEncodingException {
        return value != null ? URLEncoder.encode(value, ENCODING)
                .replace("+", "%20")
                .replace("*", "%2A")
                .replace("%7E", "~") : null;
    }

    /**
     * 生成测试签名
     *
     * @param args
     */
    public static void main(String[] args) {
        Map<String, String> params = new HashMap<>();

        long now = System.currentTimeMillis();
        logger.info("timestamp:{}", String.valueOf(now));

        String nonce = UUID.randomUUID().toString().replace("-", "");
        logger.info("nonce:{}", nonce);

        params.put(AuthHeaders.PHOEBE_APP_KEY.getHeaderName(), "fridge");
        params.put(AuthHeaders.PHOEBE_ACCESS_KEY_ID.getHeaderName(), "2ieMNkOqrcQEiGmV");
        params.put(AuthHeaders.PHOEBE_TIMESTAMP.getHeaderName(), String.valueOf(now));
        params.put(AuthHeaders.PHOEBE_NONCE.getHeaderName(), nonce);

        String sign = SignUtil.sign("post", params, "QIy6zy1MC3qvBUrwSq4xjddwPVqUXHcg");
        logger.info("sign:{}", sign);
    }
}